package com.softserveinc.moviedbapp.provider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.BaseColumns;
import android.util.Log;

import com.softserveinc.moviedbapp.cache.MovieDBOpenHelper;
import com.softserveinc.moviedbapp.cache.tables.MovieListRefTable;
import com.softserveinc.moviedbapp.cache.tables.MovieListRefTable.MovieListRefColumns;
import com.softserveinc.moviedbapp.cache.tables.MovieListTable;
import com.softserveinc.moviedbapp.cache.tables.MovieListTable.MovieListColumns;
import com.softserveinc.moviedbapp.cache.tables.MovieSearchTable;
import com.softserveinc.moviedbapp.cache.tables.MovieSearchTable.MovieSearchColumns;
import com.softserveinc.moviedbapp.cache.tables.MovieTable;
import com.softserveinc.moviedbapp.cache.tables.MovieTable.MovieColumns;
import com.softserveinc.moviedbapp.connection.GeneralException;
import com.softserveinc.moviedbapp.manager.DownloadManager;
import com.softserveinc.moviedbapp.model.Movie;
import com.softserveinc.moviedbapp.util.MovieAppConstants;

import java.util.List;

public class MovieListProvider extends ContentProvider {

	public static final String TAG = MovieListProvider.class.getSimpleName();

	public static final String AUTHORITY = "com.softserveinc.moviedbapp.movielistprovider";
	public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
	public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "movies");
	public static final String MOVIE_LIST_MIME_TYPE = "vnd.android.cursor.dir/vnd.softserveinc.movies";
	public static final String SINGLE_MOVIE_MIME_TYPE = "vnd.android.cursor.item/vnd.softserveinc.movies";

	public static final String SELECT_MOVIE_LAST_MODIFIED_AT_QUERY = "SELECT " + MovieColumns.MOVIE_LAST_MODIFIED_AT
			+ " FROM " + MovieTable.TABLE_NAME + " WHERE " + MovieColumns.MOVIE_ID + " = ?";

	private static final String SELECT_1_FROM_MOVIE_LIST_REF = "SELECT 1 FROM " + MovieListRefTable.TABLE_NAME
			+ " WHERE " + MovieColumns.MOVIE_ID + " = ? AND " + MovieListRefColumns.LIST_ID + " = ?";

	public static final String SELECT_MOVIES_BASE_QUERY = "SELECT " + MovieTable.TABLE_NAME + ".* FROM "
			+ MovieTable.TABLE_NAME + " INNER JOIN " + MovieListRefTable.TABLE_NAME + " ON " + MovieTable.TABLE_NAME
			+ "." + MovieColumns.MOVIE_ID + " = " + MovieListRefTable.TABLE_NAME + "." + MovieListRefColumns.MOVIE_ID
			+ " WHERE " + MovieListRefTable.TABLE_NAME + "." + MovieListRefColumns.LIST_ID + " = ";

	public static final String SELECT_LIST_ID_QUERY = "SELECT " + BaseColumns._ID + ", "
			+ MovieListColumns.LIST_REFRESHED_AT + " FROM " + MovieListTable.TABLE_NAME + " WHERE "
			+ MovieListColumns.LIST_TYPE + " = ";

	public static final String SELECT_LAST_SEARCH_QUERY = "SELECT " + MovieSearchColumns.SEARCH_QUERY + " FROM "
			+ MovieSearchTable.TABLE_NAME;

	public static final String SELECT_INVALID_LIST_ID = "SELECT " + BaseColumns._ID + " FROM "
			+ MovieListTable.TABLE_NAME + " WHERE " + MovieListColumns.LIST_TYPE + " = ? AND " + BaseColumns._ID
			+ " <> ?";

	public static final String SELECT_NOT_VALID_MOVIE_IDS = "SELECT " + MovieColumns.MOVIE_ID + " FROM "
			+ MovieListRefTable.TABLE_NAME + " WHERE " + MovieListRefColumns.LIST_ID + " = ";

	public static final String SELECT_CHECK_IF_MOVIE_ID_EXISTS = "SELECT 1 FROM " + MovieListRefTable.TABLE_NAME
			+ " WHERE " + MovieListRefColumns.MOVIE_ID + " = ";

	public static final String SELECT_MOVIE_IDS = "SELECT " + MovieColumns.MOVIE_ID + " FROM "
			+ MovieListRefTable.TABLE_NAME;

	private final static int LIST = 1;
	private final static int LIST_ID = 2;
	private final static int MOVIE_SEARCH = 3;

	private final static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
	static {
		sUriMatcher.addURI(AUTHORITY, "movies/search/*", MOVIE_SEARCH);
		sUriMatcher.addURI(AUTHORITY, "movies", LIST);
		sUriMatcher.addURI(AUTHORITY, "movies/#", LIST_ID);
	}

	private final static long LIST_UPDATE_PERIOD = 1800000L/* 1800000L */;
	private final static long SEARCH_LIST_UPDATE_PERIOD = 1800000L/* 1800000L */;

	private MovieDBOpenHelper dbHelper;
	private SQLiteDatabase dataBase;
	private DownloadManager downloadManager;

	@Override
	public boolean onCreate() {
		Log.d(TAG, "onCreate() ");
		dbHelper = new MovieDBOpenHelper(getContext());
		downloadManager = new DownloadManager();
		dataBase = dbHelper.getWritableDatabase();
		return true;
	}

	@Override
	public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs,
			final String sortOrder) {
		Log.d(TAG, "inside query()");

		String language = selectionArgs[0];
		String parserMode = selectionArgs[1];
		String page = "";
		String results = "";
		String listType = "";
		switch (sUriMatcher.match(uri)) {
			case LIST:
				Log.d(TAG, "URI = " + uri.toString());
				page = selectionArgs[2];
				results = selectionArgs[3];
				listType = selectionArgs[4];

				try {
					return getMovieCursor(language, parserMode, listType, page, results);
				} catch (GeneralException e) {
					Log.e(TAG, "No connection apparently", e);
					android.os.Process.killProcess(android.os.Process.myPid());
					// AlertDialog.Builder builder = new AlertDialog.Builder(getContext().getApplicationContext());
					// /* inform the user that there's no connection */
					// builder.setMessage(R.string.no_connection).setCancelable(false)
					// .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
					// @Override
					// public void onClick(final DialogInterface dialog, final int id) {
					// android.os.Process.killProcess(android.os.Process.myPid());
					// }
					// }).show();
				}

			case LIST_ID:
				page = selectionArgs[2];
				results = selectionArgs[3];
				Log.d(TAG, "URI = " + uri.toString());
				listType = getListType((int) ContentUris.parseId(uri));

				try {
					return getMovieCursor(language, parserMode, listType, page, results);
				} catch (GeneralException e) {
					Log.e(TAG, "No connection apparently", e);
					android.os.Process.killProcess(android.os.Process.myPid());
				}

			case MOVIE_SEARCH:
				Log.d(TAG, "URI = " + uri.toString());
				String searchQuery = uri.getLastPathSegment();
				int listTypeInt = MovieListType.SEARCH.getListTypeInt();
				try {
					return getMovieCursorForSearch(listTypeInt, language, parserMode, searchQuery);
				} catch (GeneralException e) {
					Log.e(TAG, "No connection apparently", e);
					android.os.Process.killProcess(android.os.Process.myPid());
				}

			case UriMatcher.NO_MATCH:
			default:
				throw new IllegalArgumentException("unrecognized URI " + uri);
		}

	}

	@Override
	public int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
		return 0;
	}

	@Override
	public Uri insert(final Uri uri, final ContentValues values) {
		return null;
	}

	@Override
	public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
		return 0;
	}

	@Override
	public String getType(final Uri uri) {
		return getId(uri) < 0 ? MOVIE_LIST_MIME_TYPE : SINGLE_MOVIE_MIME_TYPE;
	}

	private int getListTypeInt(final String listType) {
		int listTypeInt;
		if (listType.equals(MovieAppConstants.ORDER_BY_TITLE)) {
			listTypeInt = MovieListType.TITLE.getListTypeInt();
		} else if (listType.equals(MovieAppConstants.ORDER_BY_RELEASE)) {
			listTypeInt = MovieListType.RELEASE_DATE.getListTypeInt();
		} else {
			listTypeInt = MovieListType.RATING.getListTypeInt();
		}
		return listTypeInt;
	}

	private String getListType(final int listTypeInt) {
		String listType;
		if (listTypeInt == 1) {
			listType = MovieAppConstants.ORDER_BY_TITLE;
		} else if (listTypeInt == 2) {
			listType = MovieAppConstants.ORDER_BY_RELEASE;
		} else {
			listType = MovieAppConstants.ORDER_BY_RATING;
		}
		return listType;
	}

	private long getId(final Uri uri) {
		String lastPathSegment = uri.getLastPathSegment();
		if (lastPathSegment != null) {
			try {
				return Long.parseLong(lastPathSegment);
			} catch (NumberFormatException e) {
				Log.e(TAG, "TT ", e);
			}
		}
		return -1;
	}

	private Cursor getMovieCursor(final String language, final String parserType, final String listType,
			final String page, final String results) throws GeneralException {
		Cursor cursor;
		int listTypeInt = getListTypeInt(listType);
		/* get listId for this type of list */
		Log.d(TAG, "running " + SELECT_LIST_ID_QUERY + listTypeInt);
		cursor = dataBase.rawQuery(SELECT_LIST_ID_QUERY + listTypeInt, null);
		long listId = -1;
		if (cursor.moveToFirst()) {
			Log.d(TAG, "getting movie list cursor from database");
			listId = cursor.getLong(0);
			long refreshedAt = cursor.getLong(1);
			cursor.close();
			long now = System.currentTimeMillis();
			/* if 30 mins hasn't passed yet - don't update the list */
			Log.d(TAG, "From last update " + String.format("%.2f", (now - refreshedAt) / 1000.0 / 60.0)
					+ " mins passed");
			if (now - refreshedAt < LIST_UPDATE_PERIOD) {
				String orderByQuery = getOrderByQuery(listType);
				String sqlSelectMoviesBaseQuery = SELECT_MOVIES_BASE_QUERY + listId + orderByQuery;
				Log.d(TAG, "running " + sqlSelectMoviesBaseQuery);
				cursor = dataBase.rawQuery(sqlSelectMoviesBaseQuery, null);
				/* only return cursor if it has a needed page */
				if (cursor.getCount() / 10 >= Integer.parseInt(page)) {
					return cursor;
				}
			}
		} else {
			/* new list, need to insert into movie_list */
			ContentValues values = new ContentValues();
			values.put(MovieListColumns.LIST_TYPE, listTypeInt);
			listId = dataBase.insert(MovieListTable.TABLE_NAME, null, values);
			Log.d(TAG, "new list, listId =  " + listId);
		}
		cursor.close();

		Log.d(TAG, "going to download movies");
		List<Movie> movies = downloadManager.getMovieList(language, parserType, listType, page, results);

		/* build matrix cursor out of this list of movies */
		MatrixCursor martixCursor = buildMatrixCursor(movies);
		/* update data set in the database with new movies if needed */
		ListWrapper listWrapper = new ListWrapper();
		listWrapper.setMovies(movies);
		listWrapper.setListId(listId);
		new MovieUpdateDataSet().execute(listWrapper);

		/* finally return matrix cursor */
		return martixCursor;
	}

	private String getOrderByQuery(final String listType) {
		StringBuilder orderByQuery = new StringBuilder(" ORDER BY ");
		if (listType.equals(MovieAppConstants.ORDER_BY_TITLE)) {
			orderByQuery.append(MovieColumns.MOVIE_NAME).append(" ").append(MovieAppConstants.ORDER_ASC);
			return orderByQuery.toString();
		} else if (listType.equals(MovieAppConstants.ORDER_BY_RELEASE)) {
			orderByQuery.append(MovieColumns.MOVIE_RELEASED).append(" ").append(MovieAppConstants.ORDER_DESC);
			return orderByQuery.toString();
		} else {
			orderByQuery.append(MovieColumns.MOVIE_RATING).append(" ").append(MovieAppConstants.ORDER_DESC);
			return orderByQuery.toString();
		}
	}

	private MatrixCursor buildMatrixCursor(final List<Movie> movies) {
		MatrixCursor matrixCursor = new MatrixCursor(new String[] { MovieColumns.MOVIE_ID, MovieColumns.MOVIE_NAME,
				MovieColumns.MOVIE_CERTIFICATION, MovieColumns.MOVIE_VOTES, MovieColumns.MOVIE_RATING,
				MovieColumns.MOVIE_RELEASED, MovieColumns.MOVIE_URL, MovieColumns.MOVIE_POSTER_URL });

		for (Movie movie : movies) {
			matrixCursor.newRow().add(movie.getMovieId()).add(movie.getName()).add(movie.getCertification())
					.add(movie.getVotes()).add(movie.getRating()).add(movie.getReleased()).add(movie.getUrl())
					.add(movie.getThumbPosterUrl());
		}
		return matrixCursor;
	}

	/* insert a single row into movie table */
	private void insertMovie(final Movie movie, final long listId) {
		ContentValues values = new ContentValues();
		/* insert the movie record if it doesn't already exist */
		if (!movieRecordExists(movie)) {
			values.put(MovieColumns.MOVIE_ID, movie.getMovieId());
			values.put(MovieColumns.MOVIE_NAME, movie.getName());
			values.put(MovieColumns.MOVIE_CERTIFICATION, movie.getCertification());
			values.put(MovieColumns.MOVIE_VOTES, movie.getVotes());
			values.put(MovieColumns.MOVIE_RATING, movie.getRating());
			values.put(MovieColumns.MOVIE_RELEASED, movie.getReleased());
			values.put(MovieColumns.MOVIE_LAST_MODIFIED_AT, movie.getLastModifiedAt());
			values.put(MovieColumns.MOVIE_URL, movie.getUrl());
			values.put(MovieColumns.MOVIE_POSTER_URL, movie.getThumbPosterUrl());

			Log.d(TAG, "inserting into \"movie\"");
			dataBase.insert(MovieTable.TABLE_NAME, null, values);
			values.clear();
		}
		/* insert movieId and listId into movie_list_ref */
		if (!movieListRefExists(movie.getMovieId(), listId)) {
			values.put(MovieColumns.MOVIE_ID, movie.getMovieId());
			values.put(MovieListColumns.LIST_ID, listId);
			Log.d(TAG, "inserting into \"movie_list_ref\"");
			dataBase.insert(MovieListRefTable.TABLE_NAME, null, values);
		}
	}

	private boolean movieListRefExists(final int movieId, final long listId) {
		Cursor cursor = dataBase.rawQuery(SELECT_1_FROM_MOVIE_LIST_REF,
				new String[] { String.valueOf(movieId), String.valueOf(listId) });
		if (cursor.moveToFirst()) {
			cursor.close();
			return true;
		}
		cursor.close();
		return false;
	}

	private boolean movieRecordExists(final Movie movie) {
		Cursor cursor = dataBase.rawQuery(SELECT_MOVIE_LAST_MODIFIED_AT_QUERY,
				new String[] { String.valueOf(movie.getMovieId()) });
		Log.d(TAG, "checking if movie exist and is up to date");
		if (cursor.moveToFirst()) {
			Log.d(TAG, "movie exists");
			String lastModifiedAt = cursor.getString(cursor.getColumnIndex(MovieColumns.MOVIE_LAST_MODIFIED_AT));
			cursor.close();
			if (lastModifiedAt.equals(movie.getLastModifiedAt())) {
				Log.d(TAG, "dates are the same");
				return true;
			}
			Log.d(TAG, "dates are not the same, gonna update");
			updateMovie(movie);
			return true;
		}
		Log.d(TAG, "movie doesn't even exist");
		cursor.close();
		return false;
	}

	private long updateMovie(final Movie movie) {
		/* update movie */
		ContentValues values = new ContentValues();
		values.put(MovieColumns.MOVIE_NAME, movie.getName());
		values.put(MovieColumns.MOVIE_CERTIFICATION, movie.getCertification());
		values.put(MovieColumns.MOVIE_VOTES, movie.getVotes());
		values.put(MovieColumns.MOVIE_RATING, movie.getRating());
		values.put(MovieColumns.MOVIE_RELEASED, movie.getReleased());
		values.put(MovieColumns.MOVIE_LAST_MODIFIED_AT, movie.getLastModifiedAt());
		values.put(MovieColumns.MOVIE_URL, movie.getUrl());
		values.put(MovieColumns.MOVIE_POSTER_URL, movie.getThumbPosterUrl());

		Log.d(TAG, "updating \"movie\" for movieId" + movie.getMovieId());
		return dataBase.update(MovieTable.TABLE_NAME, values, MovieColumns.MOVIE_ID + " = " + movie.getMovieId(), null);
	}

	/**
	 * first check if such a search query already exists, than check if it's not old. If ok return else download new
	 * list, update movie_seach table and movie_list with fresh time, re-query and return
	 *
	 * @return cursor with a set of rows that represent search results for searchQuery
	 * @throws GeneralException
	 */
	private Cursor getMovieCursorForSearch(final int listTypeInt, final String language, final String parserMode,
			final String searchQuery) throws GeneralException {
		Log.d(TAG, "running " + SELECT_LAST_SEARCH_QUERY);
		Cursor cursor = dataBase.rawQuery(SELECT_LAST_SEARCH_QUERY, null);
		long listId = -1;
		if (cursor.moveToFirst()) {
			String query = cursor.getString(cursor.getColumnIndex(MovieSearchColumns.SEARCH_QUERY));
			cursor.close();

			/* get listId for this type of list */
			Log.d(TAG, "running " + SELECT_LIST_ID_QUERY + listTypeInt);
			cursor = dataBase.rawQuery(SELECT_LIST_ID_QUERY + listTypeInt, null);
			cursor.moveToFirst();
			listId = cursor.getLong(0);
			long refreshedAt = cursor.getLong(1);
			cursor.close();
			if (query.equals(searchQuery)) {
				/* if 30 mins hasn't passed yet - don't update the search results */
				long now = System.currentTimeMillis();
				Log.d(TAG, "From last update " + String.format("%.2f", (now - refreshedAt) / 1000.0 / 60.0)
						+ " mins passed");
				if (now - refreshedAt < SEARCH_LIST_UPDATE_PERIOD) {
					/* get listId for this type of list */
					String sqlSelectMoviesBaseQuery = SELECT_MOVIES_BASE_QUERY + listId;
					Log.d(TAG, "running " + sqlSelectMoviesBaseQuery);
					cursor = dataBase.rawQuery(sqlSelectMoviesBaseQuery, null);
					return cursor;
				}
			}
		} else {
			/* insert search type into movie_list */
			cursor.close();
			ContentValues values = new ContentValues();
			values.put(MovieListColumns.LIST_TYPE, listTypeInt);

			Log.d(TAG, "getMovieCursorForSearch() inserting into \"movie_list\"");
			listId = dataBase.insert(MovieListTable.TABLE_NAME, null, values);

			/* insert search query into movie_search */
			values.clear();
			values.put(MovieSearchColumns.SEARCH_QUERY, searchQuery);
			Log.d(TAG, "getMovieCursorForSearch() inserting into \"movie_search\"");
			dataBase.insert(MovieSearchTable.TABLE_NAME, null, values);
		}
		List<Movie> searchResults = downloadManager.searchForMovie(language, parserMode, searchQuery);

		/* if nothing founds construct return a cursor with that info */
		MatrixCursor matrixCursor;
		if (searchResults.get(0).getName().equals("Nothing found.")) {
			matrixCursor = new MatrixCursor(new String[] { MovieColumns.MOVIE_ID, MovieColumns.MOVIE_NAME,
					MovieColumns.MOVIE_CERTIFICATION, MovieColumns.MOVIE_VOTES, MovieColumns.MOVIE_RATING,
					MovieColumns.MOVIE_RELEASED, MovieColumns.MOVIE_LAST_MODIFIED_AT, MovieColumns.MOVIE_POSTER_URL });
			matrixCursor.newRow().add(0).add("Nothing found.").add("NR");
			return matrixCursor;
		} else {
			matrixCursor = buildMatrixCursor(searchResults);
		}

		/* update movie_search search query */
		ContentValues values = new ContentValues();
		values.put(MovieSearchColumns.SEARCH_QUERY, searchQuery);
		Log.d(TAG, "getMovieCursorForSearch() update \"movie_search\" " + searchQuery);
		dataBase.update(MovieSearchTable.TABLE_NAME, values, BaseColumns._ID + " = " + 1, null);

		ListWrapper wrapper = new ListWrapper();
		wrapper.setListId(listId);
		wrapper.setMovies(searchResults);
		new MovieUpdateDataSet().execute(wrapper);

		return matrixCursor;
	}

	private class MovieUpdateDataSet extends AsyncTask<ListWrapper, Void, Void> {

		private final String TAG = MovieUpdateDataSet.class.getSimpleName();

		@Override
		protected Void doInBackground(final ListWrapper... params) {
			List<Movie> movies = params[0].getMovies();
			long listId = params[0].getListId();
			/* insert into movie_list new refresh time */
			long now = System.currentTimeMillis();
			ContentValues values = new ContentValues();
			values.put(MovieListColumns.LIST_REFRESHED_AT, now);
			Log.d(TAG, "updating \"movie_list\" with listId = " + listId);
			dataBase.update(MovieListTable.TABLE_NAME, values, MovieListColumns._ID + " = " + listId, null);

			/* delete from movie_list_table all occurrences of rows with this listId */
			// mDataBase.delete(MovieListRefTable.TABLE_NAME, MovieListRefColumns.LIST_ID + " = " + listId, null);

			/* insert movies in table movie, and movieId and listId into the movie_list_ref table */
			for (Movie movie : movies) {
				insertMovie(movie, listId);
			}
			/* delete the movies that are not in any of the lists */
			// mDataBase.delete(MovieTable.TABLE_NAME, MovieColumns.MOVIE_ID + " NOT IN (" + SELECT_MOVIE_IDS + ")",
			// null);
			return null;
		}
	}
}
